/**
 * Copyright 2011 ArcBees Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.gwtplatform.dispatch.annotation.helper;

import java.io.Writer;
import java.util.Collection;
import java.util.Set;

import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeMirror;

/**
 * {@link ClassGenerationHelper} is an internal class that provides common routines
 * only used by the annotation processors.
 *
 * @author Brendan Doherty
 * @author Florian Sauter
 * @author Stephen Haberman (concept)
 */
public class ClassGenerationHelper extends GenerationHelper {

  public ClassGenerationHelper(Writer sourceWriter) {
    super(sourceWriter);
  }

  /**
   * Generates a class header. Pass null to skip the parent class.
   *
   * <p>
   * <b>Usage:</b>
   * </p>
   *
   * <pre>
   * <code>
   *  generateClassHeader(Foo.class.getSimpleName(),
   *        HasName.class.getSimpleName,
   *        "MyGenericInterface{@literal <Foo>}"
   *  )
   *  </code>
   * </pre>
   * <p>
   * <b>Generated example:</b>
   * </p>
   *
   * <pre>
   * <code>public class MyFoo extends Foo implements HasName, MyGenericInterface{@literal <Foo>} {</code></pre>
   *
   * @param className the simple class name
   * @param modifiers the modifiers for the class
   * @param extendedClassName the parent class name
   * @param extraInterfaces array of interface names to be implemented
   */
  public void generateClassHeader(String className, String extendedClassName, Set<Modifier> modifiers, String... extraInterfaces) {
    println();
    print("{0}class {1}", generateModifierList(modifiers.toArray(new Modifier[]{})), className);
    if (extendedClassName != null && !extendedClassName.isEmpty()) {
      String trimedExtendedClassName = extendedClassName.trim();
      printWithoutSpaces(" extends {0}", trimedExtendedClassName);
    }
    if (extraInterfaces != null && extraInterfaces.length > 0) {
      printWithoutSpaces(" implements {0}", implode(extraInterfaces, ","));
    }
    printWithoutSpaces(" { ");
  }

  public void generateConstructorUsingFields(String simpleClassName, Collection<VariableElement> fieldsToBePassedAndAssigned, Modifier... constructorModifiers) {
    println();
    println("  {0}{1}({2}) {",
        generateModifierList(constructorModifiers),
        simpleClassName,
        generateFieldList(fieldsToBePassedAndAssigned, true, false)
    );
    if (fieldsToBePassedAndAssigned != null) {
      for (VariableElement fieldToBeAssigned : fieldsToBePassedAndAssigned) {
        generateFieldAssignment(fieldToBeAssigned, fieldToBeAssigned.getSimpleName().toString());
      }
    }
    println("  }");
  }

  public void generateEmptyConstructor(String simpleClassName, Modifier modifier) {
    println();
    if (modifier != null) {
      println("  {0} {1}() {", modifier, simpleClassName);
    } else {
      println("  {0}() {", simpleClassName);
    }
    println("    // Possibly for serialization.");
    println("  }");
  }

  /**
   * Creates an equals method using Java standards.
   *
   * @param simpleClassName the class name
   * @param fieldElements the field elements
   */
  public void generateEquals(String simpleClassName, Collection<VariableElement> fieldElements) {
    println();
    println("  @Override");
    println("  public boolean equals(Object obj) {");
    if (fieldElements.size() > 0) {
      println("    if (this == obj)");
      println("        return true;");
      println("    if (obj == null)");
      println("        return false;");
      println("    if (getClass() != obj.getClass())");
      println("        return false;");
      println("    {0} other = ({0}) obj;", simpleClassName);

      for (VariableElement fieldElement : fieldElements) {
        TypeMirror type = fieldElement.asType();
        String fieldName = fieldElement.getSimpleName().toString();
        if (!isStatic(fieldElement)) {
          if (isPrimitive(type)) {
            println("    if ({0} != other.{0})", fieldName);
            println("        return false;");
          } else {
            println("    if ({0} == null) {", fieldName);
            println("      if (other.{0} != null)", fieldName);
            println("        return false;");
            println("    } else if (!{0}.equals(other.{0}))", fieldName);
            println("      return false;");
          }
        }
      }
      println("    return true;");
    } else {
      println("    return super.equals(obj);");
    }
    println("  }");
  }

  public void generateFieldAccessors(Collection<VariableElement> fieldElements) {
    for (VariableElement fieldElement : fieldElements) {
      println();
      println("  {0} {1} {2}(){", Modifier.PUBLIC, fieldElement.asType().toString(), manufactureAccessorName(fieldElement));
      println("    return {0};", fieldElement.getSimpleName());
      println("  }");
    }
  }

  /**
   * Creates a default field assignment.
   *
   * <p>
   * <code>generateFieldAssignment(myNameIsTest, null)</code>
   * </p>
   *
   * Generates:
   *
   * <p>
   * <code>    this.test = null;</code>
   * </p>
   *
   * @param fieldElement
   * @param value
   */
  public void generateFieldAssignment(VariableElement fieldElement, Object value) {
    println("    this.{0} = {1};", fieldElement.getSimpleName(), String.valueOf(value));
  }

  /**
   * Creates an hashCode method using Java standards.
   *
   * @param fieldElements the field elements
   */
  public void generateHashCode(Collection<VariableElement> fieldElements) {
    println();
    println("  @Override");
    println("  public int hashCode() {");
    if (fieldElements.size() > 0) {
      println("    int hashCode = 23;");
      for (VariableElement fieldElement : fieldElements) {
        TypeMirror type = fieldElement.asType();
        String fieldName = fieldElement.getSimpleName().toString();
        if (!isStatic(fieldElement)) {
          if (type instanceof ArrayType) {
            // hashCode = (hashCode * 37) + java.util.Arrays.deepHashCode(banana);
            println("    hashCode = (hashCode * 37) + java.util.Arrays.deepHashCode({0});", fieldName);
          } else if (isPrimitive(type)) {
            // hashCode = (hashCode * 37) + new Integer(height).hashCode();
            println("    hashCode = (hashCode * 37) + new {0}({1}).hashCode();", determineWrapperClass(type), fieldName);
          } else {
            // hashCode = (hashCode * 37) + (blah == null ? 1 : blah.hashCode());
            println("    hashCode = (hashCode * 37) + ({0} == null ? 1 : {0}.hashCode());", fieldName);
          }
        }
      }
      println("    return hashCode;");
    } else {
      println("    return super.hashCode();");
    }
    println("  }");
  }

  public void generateToString(String simpleClassName, Collection<VariableElement> fieldElements) {
    println();

    println("  @Override");
    println("  public String toString() {");
    println("    return \"" + simpleClassName + "[\"");
    int i = 0;
    for (VariableElement fieldElement : fieldElements) {
      if (i++ > 0) {
        println("                 + \",\"");
      }
      println("                 + " + fieldElement.getSimpleName());
    }
    println("    + \"]\";");
    println("  }");
  }

}
